#include <iostream>
#include <string>
#include <pthread.h>
#include <fstream>
#include <vector>
#include <sstream>
#include <sys/types.h>
#include <sys/socket.h>
#include <unordered_map>
#include "Socket.hpp"
#include "log.hpp"
//图片，首页、资源存储在wwwroot目录
const std::string wwwroot="./wwwroot"; // 这个路径表示当前目录下的wwwroot目录作为web 根目录
const std::string sep = "\r\n";//分隔符
const std::string homepage = "Product_page.html";//首页路径

static const int defaultport = 8082;

class HttpServer;

//线程传参所用结构体
class ThreadData
{
public:
    ThreadData(int fd, HttpServer *s) : sockfd(fd), svr(s)
    {
    }

public:
    int sockfd;
    HttpServer *svr;
};
//自定义http协议
class HttpRequest
{
public:
    //反序列化函数，把一大行字符串打散，分别提取其中的关键信息，本质是字符串切割
    void Deserialize(std::string req)//传入一个分割符
    {
        while(true)
        {
            std::size_t pos = req.find(sep);//查找分隔符
            if(pos == std::string::npos) break;//说明没找到分隔符，没有这个位置

            //找到的第一组/r/n，这组分隔符前面的字符串一定是请求行或响应行的内容
            //对请求行或响应行的字符串做切割
            std::string temp = req.substr(0, pos);


            //处理报头和有效载荷的空行
            if(temp.empty()) break; //因为在while循环，一直截取，直到temp存储了空行，是空行就break
            req_header.push_back(temp);//把报头里一行一行的内容push到vector容器里

            //把已经push入vector的行在一大串的字符串中移除掉
            req.erase(0, pos+sep.size());//把末尾的分隔符也移除掉
        }
        //经过while循环处理后 req的请求行/相应行+报头已经被去除，剩下的全是报文，把报文给text
        text = req;
    }
    // .png:image/png 只解析请求行或响应行用的函数
    void Parse()
    {
        //stringstream类型是用来分割字符串的
        std::stringstream ss(req_header[0]);//vecor容器的第一个元素就是请求行，包含了请求行的所有信息
        //打散大行字符串，分割为小型字符串，注意切割的顺序不能改变
        ss >> method >> url >> http_version;//http协议规定了第一个就是方法，第二个就是url，第三个就是http版本
        file_path = wwwroot; // 默认path是./wwwroot，无论你请求的路径是什么，我都默认从根目录下开始找并帮你拼接具体路径
        if(url == "/" || url == "/index.html") {//如果请求的是根目录或者请求的是我的首页
            file_path += "/";
            file_path += homepage; // ./wwwroot/index.html，我就给你返回我的首页
        }
        else file_path += url; // 如果不是请求我的首页或根目录，就把具体的路径拼接，然后给你/a/b/c/d.html->./wwwroot/a/b/c/d.html

        auto pos = file_path.rfind(".");//从后往前找.符号
        if(pos == std::string::npos) suffix = ".html";//如果找不到，后缀默认设置为.html
        else suffix = file_path.substr(pos);//找到了说明有后缀，把后缀赋值非suffix变量
    }
    void DebugPrint()
    {
        for(auto &line : req_header)
        {
            std::cout << "--------------------------------" << std::endl;
            std::cout << line << "\n\n";
        }

        std::cout << "method: " << method << std::endl;
        std::cout << "url: " << url << std::endl;
        std::cout << "http_version: " << http_version << std::endl;
        std::cout << "file_path: " << file_path << std::endl;
        std::cout << text << std::endl;
    }
public:
    std::vector<std::string> req_header;//收容报头的一行一行属性的容器
    std::string text;

    // 解析之后的结果,提取请求行的具体信息
    std::string method;
    std::string url;
    std::string http_version;//上面这3个变量要的资源全在请求行，也就是说，全在vector容器的第0个元素位置
    std::string file_path; // ./wwwroot/a/b/c.html 2.png这个变量用来保存资源的路径

    std::string suffix;//表示文件后缀
    
};

class HttpServer
{
public:
    HttpServer(uint16_t port = defaultport) : port_(port)
    {
        //如果后缀是.html，那么报头里的content-type属性值就是text/html 
        content_type.insert({".html", "text/html"});//添加进容器
        content_type.insert({".png", "image/png"});//如果后缀是图片，content-type属性就是image/png
    }
    bool Start()
    {
        listensock_.Socket();//创建套接字
        listensock_.Bind(port_);//绑定套接字
        listensock_.Listen();//监听客户端发送的请求
        for (;;)
        {
            std::string clientip;
            uint16_t clientport;
            int sockfd = listensock_.Accept(&clientip, &clientport);//接收客户端发来的请求，输入两个输出型参数
            if (sockfd < 0)
                continue;
            lg(Info, "获取到一个新的连接, sockfd: %d", sockfd);
            std::cout<<"该获取的新的连接，它的来源Ip地址为: "<<clientip<<std::endl;
            pthread_t tid;
            ThreadData *td = new ThreadData(sockfd, this);
            pthread_create(&tid, nullptr, ThreadRun, td);//创建一个线程，由该线程去执行发送信息和接收信息逻辑
        }
    }
    //根据传入的路径参数读取具体路径的资源
    static std::string ReadHtmlContent(const std::string &htmlpath)
    {
        // 坑
        std::ifstream in(htmlpath, std::ios::binary);//以二进制方式读取一个文件，通常用于音视频
        if(!in.is_open()) return "";//如果打开失败
        in.seekg(0, std::ios_base::end);//设置输入流的读取位置，第一个参数0表示从文件开头计算，第二个参数表示读到末尾
        auto len = in.tellg();
        in.seekg(0, std::ios_base::beg);//再把读取文件的位置定位到文件最开始

        std::string content;//用来你读取到的二进制文件的内容变量
        content.resize(len);//先把变量的大小设置为len 

        in.read((char*)content.c_str(), content.size());//把读取到的二进制文件内容存储以字符串方式到content变量里

        in.close();
        return content;//把读取到的资源、html语句以一大行字符串的方式存储并返回
    }
    //把后缀转成描述的函数
    std::string SuffixToDesc(const std::string &suffix)
    {
        //从content_type容器变量中查找后缀
        auto iter = content_type.find(suffix);
        if(iter == content_type.end()) return content_type[".html"];//没找到，就返回对应key的string值
        else return content_type[suffix];//找到了，返回key键对应的value值
    }
    //读取前端页面用的函数
    void HandlerHttp(int sockfd)//传入一个套接字描述符
    {
        char buffer[10240];
        ssize_t n = recv(sockfd, buffer, sizeof(buffer) - 1, 0); // bug,通过套接字读取http客户端的信息，存储到Buffer里
        if (n > 0)
        {
            buffer[n] = 0;
            std::cout << buffer << std::endl; // 假设我们读取到的就是一个完整的，独立的http请求,存在buffer里面
           
            HttpRequest req;//定义一个对象，调用它的反序列化函数
            req.Deserialize(buffer);//对buffer进行反序列化
            req.Parse();//分割字符串并把路径信息存入req对象里
            req.DebugPrint();


            // 返回响应的过程
            std::string text;
            bool ok = true;
            text = ReadHtmlContent(req.file_path); // 失败？
            //用户输入的url信息有错误
            if(text.empty())//就让用户读取到404页面，这个页面由程序员定义
            {
                ok = false;
                std::string err_html = wwwroot;
                err_html += "/";
                err_html += "err.html";//提供404页面路径
                text = ReadHtmlContent(err_html);
            }
            std::string response_line;//存储响应行变量
            if(ok)
                response_line = "HTTP/1.0 200 OK\r\n";//状态码描述
            else//如果不OK
                response_line = "HTTP/1.0 404 Not Found\r\n";
            

            //存储报头属性
            std::string response_header = "Content-Length: ";//正文长度
            response_header += std::to_string(text.size()); // Content-Length: 11
            response_header += "\r\n";
            response_header += "Content-Type: ";//是text还是html，有些老的浏览器不知道，需要显式的告知它
            response_header += SuffixToDesc(req.suffix);
            response_header += "\r\n";
            response_header += "Set-Cookie: name=haha";
            response_header += "\r\n";

            std::string blank_line = "\r\n"; // \n
            //存储整个报文信息
            std::string response = response_line;
            response += response_header;
            response += blank_line;
            response += text;

            send(sockfd, response.c_str(), response.size(), 0);
        }
        std::cout<<"运行到这，说明要关闭网络套接字"<<std::endl;
        close(sockfd);
    }
    static void *ThreadRun(void *args)
    {
        pthread_detach(pthread_self());
        ThreadData *td = static_cast<ThreadData *>(args);
        
        td->svr->HandlerHttp(td->sockfd);

        delete td;
        return nullptr;
    }
    ~HttpServer()
    {
    
    }

private:
    Sock listensock_;
    uint16_t port_;
    std::unordered_map<std::string, std::string> content_type;//记录内容类型的容器变量
};
